/**
 * Copyright (C) 2008 Happy Fish / YuQing
 *
 * FastDFS Java Client may be copied only under the terms of the GNU Lesser
 * General Public License (LGPL).
 * Please visit the FastDFS Home Page http://www.csource.org/ for more detail.
 */

package org.csource.fastdfs;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;

/**
 * Tracker client
 * 
 * @author Happy Fish / YuQing
 * @version Version 1.19
 */
public class TrackerClient {
	protected TrackerGroup tracker_group;
	protected byte errno;

	/**
	 * constructor with global tracker group
	 */
	public TrackerClient() {
		this.tracker_group = ClientGlobal.g_tracker_group;
	}

	/**
	 * constructor with specified tracker group
	 * 
	 * @param tracker_group
	 *            the tracker group object
	 */
	public TrackerClient(TrackerGroup tracker_group) {
		this.tracker_group = tracker_group;
	}

	/**
	 * get the error code of last call
	 * 
	 * @return the error code of last call
	 */
	public byte getErrorCode() {
		return this.errno;
	}

	/**
	 * get a connection to tracker server
	 * 
	 * @return tracker server Socket object, return null if fail
	 */
	public TrackerServer getConnection() throws IOException {
		return this.tracker_group.getConnection();
	}

	/**
	 * query storage server to upload file
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @return storage server Socket object, return null if fail
	 */
	public StorageServer getStoreStorage(TrackerServer trackerServer)
			throws IOException {
		final String groupName = null;
		return this.getStoreStorage(trackerServer, groupName);
	}

	/**
	 * query storage server to upload file
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param groupName
	 *            the group name to upload file to, can be empty
	 * @return storage server object, return null if fail
	 */
	public StorageServer getStoreStorage(TrackerServer trackerServer,
			String groupName) throws IOException {
		byte[] header;
		String ip_addr;
		int port;
		byte cmd;
		int out_len;
		boolean bNewConnection;
		byte store_path;
		Socket trackerSocket;

		if (trackerServer == null) {
			trackerServer = getConnection();
			if (trackerServer == null) {
				return null;
			}
			bNewConnection = true;
		} else {
			bNewConnection = false;
		}

		trackerSocket = trackerServer.getSocket();
		OutputStream out = trackerSocket.getOutputStream();

		try {
			if (groupName == null || groupName.length() == 0) {
				cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE;
				out_len = 0;
			} else {
				cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE;
				out_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
			}
			header = ProtoCommon.packHeader(cmd, out_len, (byte) 0);
			out.write(header);

			if (groupName != null && groupName.length() > 0) {
				byte[] bGroupName;
				byte[] bs;
				int group_len;

				bs = groupName.getBytes(ClientGlobal.g_charset);
				bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];

				if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
					group_len = bs.length;
				} else {
					group_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
				}
				Arrays.fill(bGroupName, (byte) 0);
				System.arraycopy(bs, 0, bGroupName, 0, group_len);
				out.write(bGroupName);
			}

			ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(
					trackerSocket.getInputStream(),
					ProtoCommon.TRACKER_PROTO_CMD_RESP,
					ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN);
			this.errno = pkgInfo.errno;
			if (pkgInfo.errno != 0) {
				return null;
			}

			ip_addr = new String(pkgInfo.body,
					ProtoCommon.FDFS_GROUP_NAME_MAX_LEN,
					ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();

			port = (int) ProtoCommon.buff2long(pkgInfo.body,
					ProtoCommon.FDFS_GROUP_NAME_MAX_LEN
							+ ProtoCommon.FDFS_IPADDR_SIZE - 1);
			store_path = pkgInfo.body[ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN - 1];

			return new StorageServer(ip_addr, port, store_path);
		} catch (IOException ex) {
			if (!bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}

			throw ex;
		} finally {
			if (bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}
		}
	}

	/**
	 * query storage servers to upload file
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param groupName
	 *            the group name to upload file to, can be empty
	 * @return storage servers, return null if fail
	 */
	public StorageServer[] getStoreStorages(TrackerServer trackerServer,
			String groupName) throws IOException {
		byte[] header;
		String ip_addr;
		int port;
		byte cmd;
		int out_len;
		boolean bNewConnection;
		Socket trackerSocket;

		if (trackerServer == null) {
			trackerServer = getConnection();
			if (trackerServer == null) {
				return null;
			}
			bNewConnection = true;
		} else {
			bNewConnection = false;
		}

		trackerSocket = trackerServer.getSocket();
		OutputStream out = trackerSocket.getOutputStream();

		try {
			if (groupName == null || groupName.length() == 0) {
				cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ALL;
				out_len = 0;
			} else {
				cmd = ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ALL;
				out_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
			}
			header = ProtoCommon.packHeader(cmd, out_len, (byte) 0);
			out.write(header);

			if (groupName != null && groupName.length() > 0) {
				byte[] bGroupName;
				byte[] bs;
				int group_len;

				bs = groupName.getBytes(ClientGlobal.g_charset);
				bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];

				if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
					group_len = bs.length;
				} else {
					group_len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
				}
				Arrays.fill(bGroupName, (byte) 0);
				System.arraycopy(bs, 0, bGroupName, 0, group_len);
				out.write(bGroupName);
			}

			ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(
					trackerSocket.getInputStream(),
					ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
			this.errno = pkgInfo.errno;
			if (pkgInfo.errno != 0) {
				return null;
			}

			if (pkgInfo.body.length < ProtoCommon.TRACKER_QUERY_STORAGE_STORE_BODY_LEN) {
				this.errno = ProtoCommon.ERR_NO_EINVAL;
				return null;
			}

			int ipPortLen = pkgInfo.body.length
					- (ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + 1);
			final int recordLength = ProtoCommon.FDFS_IPADDR_SIZE - 1
					+ ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;

			if (ipPortLen % recordLength != 0) {
				this.errno = ProtoCommon.ERR_NO_EINVAL;
				return null;
			}

			int serverCount = ipPortLen / recordLength;
			if (serverCount > 16) {
				this.errno = ProtoCommon.ERR_NO_ENOSPC;
				return null;
			}

			StorageServer[] results = new StorageServer[serverCount];
			byte store_path = pkgInfo.body[pkgInfo.body.length - 1];
			int offset = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;

			for (int i = 0; i < serverCount; i++) {
				ip_addr = new String(pkgInfo.body, offset,
						ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();
				offset += ProtoCommon.FDFS_IPADDR_SIZE - 1;

				port = (int) ProtoCommon.buff2long(pkgInfo.body, offset);
				offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;

				results[i] = new StorageServer(ip_addr, port, store_path);
			}

			return results;
		} catch (IOException ex) {
			if (!bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}

			throw ex;
		} finally {
			if (bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}
		}
	}

	/**
	 * query storage server to download file
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param groupName
	 *            the group name of storage server
	 * @param filename
	 *            filename on storage server
	 * @return storage server Socket object, return null if fail
	 */
	public StorageServer getFetchStorage(TrackerServer trackerServer,
			String groupName, String filename) throws IOException {
		ServerInfo[] servers = this.getStorages(trackerServer,
				ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE,
				groupName, filename);
		if (servers == null) {
			return null;
		} else {
			return new StorageServer(servers[0].getIpAddr(),
					servers[0].getPort(), 0);
		}
	}

	/**
	 * query storage server to update file (delete file or set meta data)
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param groupName
	 *            the group name of storage server
	 * @param filename
	 *            filename on storage server
	 * @return storage server Socket object, return null if fail
	 */
	public StorageServer getUpdateStorage(TrackerServer trackerServer,
			String groupName, String filename) throws IOException {
		ServerInfo[] servers = this.getStorages(trackerServer,
				ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE, groupName,
				filename);
		if (servers == null) {
			return null;
		} else {
			return new StorageServer(servers[0].getIpAddr(),
					servers[0].getPort(), 0);
		}
	}

	/**
	 * get storage servers to download file
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param groupName
	 *            the group name of storage server
	 * @param filename
	 *            filename on storage server
	 * @return storage servers, return null if fail
	 */
	public ServerInfo[] getFetchStorages(TrackerServer trackerServer,
			String groupName, String filename) throws IOException {
		return this.getStorages(trackerServer,
				ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ALL,
				groupName, filename);
	}

	/**
	 * query storage server to download file
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param cmd
	 *            command code,
	 *            ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE or
	 *            ProtoCommon.TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE
	 * @param groupName
	 *            the group name of storage server
	 * @param filename
	 *            filename on storage server
	 * @return storage server Socket object, return null if fail
	 */
	protected ServerInfo[] getStorages(TrackerServer trackerServer, byte cmd,
			String groupName, String filename) throws IOException {
		byte[] header;
		byte[] bFileName;
		byte[] bGroupName;
		byte[] bs;
		int len;
		String ip_addr;
		int port;
		boolean bNewConnection;
		Socket trackerSocket;

		if (trackerServer == null) {
			trackerServer = getConnection();
			if (trackerServer == null) {
				return null;
			}
			bNewConnection = true;
		} else {
			bNewConnection = false;
		}
		trackerSocket = trackerServer.getSocket();
		OutputStream out = trackerSocket.getOutputStream();

		try {
			bs = groupName.getBytes(ClientGlobal.g_charset);
			bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];
			bFileName = filename.getBytes(ClientGlobal.g_charset);

			if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
				len = bs.length;
			} else {
				len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
			}
			Arrays.fill(bGroupName, (byte) 0);
			System.arraycopy(bs, 0, bGroupName, 0, len);

			header = ProtoCommon.packHeader(cmd,
					ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + bFileName.length,
					(byte) 0);
			byte[] wholePkg = new byte[header.length + bGroupName.length
					+ bFileName.length];
			System.arraycopy(header, 0, wholePkg, 0, header.length);
			System.arraycopy(bGroupName, 0, wholePkg, header.length,
					bGroupName.length);
			System.arraycopy(bFileName, 0, wholePkg, header.length
					+ bGroupName.length, bFileName.length);
			out.write(wholePkg);

			ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(
					trackerSocket.getInputStream(),
					ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
			this.errno = pkgInfo.errno;
			if (pkgInfo.errno != 0) {
				return null;
			}

			if (pkgInfo.body.length < ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) {
				throw new IOException("Invalid body length: "
						+ pkgInfo.body.length);
			}

			if ((pkgInfo.body.length - ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN)
					% (ProtoCommon.FDFS_IPADDR_SIZE - 1) != 0) {
				throw new IOException("Invalid body length: "
						+ pkgInfo.body.length);
			}

			int server_count = 1
					+ (pkgInfo.body.length - ProtoCommon.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN)
					/ (ProtoCommon.FDFS_IPADDR_SIZE - 1);

			ip_addr = new String(pkgInfo.body,
					ProtoCommon.FDFS_GROUP_NAME_MAX_LEN,
					ProtoCommon.FDFS_IPADDR_SIZE - 1).trim();
			int offset = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN
					+ ProtoCommon.FDFS_IPADDR_SIZE - 1;

			port = (int) ProtoCommon.buff2long(pkgInfo.body, offset);
			offset += ProtoCommon.FDFS_PROTO_PKG_LEN_SIZE;

			ServerInfo[] servers = new ServerInfo[server_count];
			servers[0] = new ServerInfo(ip_addr, port);
			for (int i = 1; i < server_count; i++) {
				servers[i] = new ServerInfo(new String(pkgInfo.body, offset,
						ProtoCommon.FDFS_IPADDR_SIZE - 1).trim(), port);
				offset += ProtoCommon.FDFS_IPADDR_SIZE - 1;
			}

			return servers;
		} catch (IOException ex) {
			if (!bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}

			throw ex;
		} finally {
			if (bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}
		}
	}

	/**
	 * query storage server to download file
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param file_id
	 *            the file id(including group name and filename)
	 * @return storage server Socket object, return null if fail
	 */
	public StorageServer getFetchStorage1(TrackerServer trackerServer,
			String file_id) throws IOException {
		String[] parts = new String[2];
		this.errno = StorageClient1.split_file_id(file_id, parts);
		if (this.errno != 0) {
			return null;
		}

		return this.getFetchStorage(trackerServer, parts[0], parts[1]);
	}

	/**
	 * get storage servers to download file
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param file_id
	 *            the file id(including group name and filename)
	 * @return storage servers, return null if fail
	 */
	public ServerInfo[] getFetchStorages1(TrackerServer trackerServer,
			String file_id) throws IOException {
		String[] parts = new String[2];
		this.errno = StorageClient1.split_file_id(file_id, parts);
		if (this.errno != 0) {
			return null;
		}

		return this.getFetchStorages(trackerServer, parts[0], parts[1]);
	}

	/**
	 * list groups
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @return group stat array, return null if fail
	 */
	public StructGroupStat[] listGroups(TrackerServer trackerServer)
			throws IOException {
		byte[] header;
		String ip_addr;
		int port;
		byte cmd;
		int out_len;
		boolean bNewConnection;
		byte store_path;
		Socket trackerSocket;

		if (trackerServer == null) {
			trackerServer = getConnection();
			if (trackerServer == null) {
				return null;
			}
			bNewConnection = true;
		} else {
			bNewConnection = false;
		}

		trackerSocket = trackerServer.getSocket();
		OutputStream out = trackerSocket.getOutputStream();

		try {
			header = ProtoCommon.packHeader(
					ProtoCommon.TRACKER_PROTO_CMD_SERVER_LIST_GROUP, 0,
					(byte) 0);
			out.write(header);

			ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(
					trackerSocket.getInputStream(),
					ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
			this.errno = pkgInfo.errno;
			if (pkgInfo.errno != 0) {
				return null;
			}

			ProtoStructDecoder<StructGroupStat> decoder = new ProtoStructDecoder<StructGroupStat>();
			return decoder.decode(pkgInfo.body, StructGroupStat.class,
					StructGroupStat.getFieldsTotalSize());
		} catch (IOException ex) {
			if (!bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}

			throw ex;
		} catch (Exception ex) {
			ex.printStackTrace();
			this.errno = ProtoCommon.ERR_NO_EINVAL;
			return null;
		} finally {
			if (bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}
		}
	}

	/**
	 * query storage server stat info of the group
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param groupName
	 *            the group name of storage server
	 * @return storage server stat array, return null if fail
	 */
	public StructStorageStat[] listStorages(TrackerServer trackerServer,
			String groupName) throws IOException {
		final String storageIpAddr = null;
		return this.listStorages(trackerServer, groupName, storageIpAddr);
	}

	/**
	 * query storage server stat info of the group
	 * 
	 * @param trackerServer
	 *            the tracker server
	 * @param groupName
	 *            the group name of storage server
	 * @param storageIpAddr
	 *            the storage server ip address, can be null or empty
	 * @return storage server stat array, return null if fail
	 */
	public StructStorageStat[] listStorages(TrackerServer trackerServer,
			String groupName, String storageIpAddr) throws IOException {
		byte[] header;
		byte[] bGroupName;
		byte[] bs;
		int len;
		boolean bNewConnection;
		Socket trackerSocket;

		if (trackerServer == null) {
			trackerServer = getConnection();
			if (trackerServer == null) {
				return null;
			}
			bNewConnection = true;
		} else {
			bNewConnection = false;
		}
		trackerSocket = trackerServer.getSocket();
		OutputStream out = trackerSocket.getOutputStream();

		try {
			bs = groupName.getBytes(ClientGlobal.g_charset);
			bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];

			if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
				len = bs.length;
			} else {
				len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
			}
			Arrays.fill(bGroupName, (byte) 0);
			System.arraycopy(bs, 0, bGroupName, 0, len);

			int ipAddrLen;
			byte[] bIpAddr;
			if (storageIpAddr != null && storageIpAddr.length() > 0) {
				bIpAddr = storageIpAddr.getBytes(ClientGlobal.g_charset);
				if (bIpAddr.length < ProtoCommon.FDFS_IPADDR_SIZE) {
					ipAddrLen = bIpAddr.length;
				} else {
					ipAddrLen = ProtoCommon.FDFS_IPADDR_SIZE - 1;
				}
			} else {
				bIpAddr = null;
				ipAddrLen = 0;
			}

			header = ProtoCommon.packHeader(
					ProtoCommon.TRACKER_PROTO_CMD_SERVER_LIST_STORAGE,
					ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ipAddrLen, (byte) 0);
			byte[] wholePkg = new byte[header.length + bGroupName.length
					+ ipAddrLen];
			System.arraycopy(header, 0, wholePkg, 0, header.length);
			System.arraycopy(bGroupName, 0, wholePkg, header.length,
					bGroupName.length);
			if (ipAddrLen > 0) {
				System.arraycopy(bIpAddr, 0, wholePkg, header.length
						+ bGroupName.length, ipAddrLen);
			}
			out.write(wholePkg);

			ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(
					trackerSocket.getInputStream(),
					ProtoCommon.TRACKER_PROTO_CMD_RESP, -1);
			this.errno = pkgInfo.errno;
			if (pkgInfo.errno != 0) {
				return null;
			}

			ProtoStructDecoder<StructStorageStat> decoder = new ProtoStructDecoder<StructStorageStat>();
			return decoder.decode(pkgInfo.body, StructStorageStat.class,
					StructStorageStat.getFieldsTotalSize());
		} catch (IOException ex) {
			if (!bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}

			throw ex;
		} catch (Exception ex) {
			ex.printStackTrace();
			this.errno = ProtoCommon.ERR_NO_EINVAL;
			return null;
		} finally {
			if (bNewConnection) {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}
		}
	}

	/**
	 * delete a storage server from the tracker server
	 * 
	 * @param trackerServer
	 *            the connected tracker server
	 * @param groupName
	 *            the group name of storage server
	 * @param storageIpAddr
	 *            the storage server ip address
	 * @return true for success, false for fail
	 */
	private boolean deleteStorage(TrackerServer trackerServer,
			String groupName, String storageIpAddr) throws IOException {
		byte[] header;
		byte[] bGroupName;
		byte[] bs;
		int len;
		Socket trackerSocket;

		trackerSocket = trackerServer.getSocket();
		OutputStream out = trackerSocket.getOutputStream();

		bs = groupName.getBytes(ClientGlobal.g_charset);
		bGroupName = new byte[ProtoCommon.FDFS_GROUP_NAME_MAX_LEN];

		if (bs.length <= ProtoCommon.FDFS_GROUP_NAME_MAX_LEN) {
			len = bs.length;
		} else {
			len = ProtoCommon.FDFS_GROUP_NAME_MAX_LEN;
		}
		Arrays.fill(bGroupName, (byte) 0);
		System.arraycopy(bs, 0, bGroupName, 0, len);

		int ipAddrLen;
		byte[] bIpAddr = storageIpAddr.getBytes(ClientGlobal.g_charset);
		if (bIpAddr.length < ProtoCommon.FDFS_IPADDR_SIZE) {
			ipAddrLen = bIpAddr.length;
		} else {
			ipAddrLen = ProtoCommon.FDFS_IPADDR_SIZE - 1;
		}

		header = ProtoCommon.packHeader(
				ProtoCommon.TRACKER_PROTO_CMD_SERVER_DELETE_STORAGE,
				ProtoCommon.FDFS_GROUP_NAME_MAX_LEN + ipAddrLen, (byte) 0);
		byte[] wholePkg = new byte[header.length + bGroupName.length
				+ ipAddrLen];
		System.arraycopy(header, 0, wholePkg, 0, header.length);
		System.arraycopy(bGroupName, 0, wholePkg, header.length,
				bGroupName.length);
		System.arraycopy(bIpAddr, 0, wholePkg, header.length
				+ bGroupName.length, ipAddrLen);
		out.write(wholePkg);

		ProtoCommon.RecvPackageInfo pkgInfo = ProtoCommon.recvPackage(
				trackerSocket.getInputStream(),
				ProtoCommon.TRACKER_PROTO_CMD_RESP, 0);
		this.errno = pkgInfo.errno;
		return pkgInfo.errno == 0;
	}

	/**
	 * delete a storage server from the global FastDFS cluster
	 * 
	 * @param groupName
	 *            the group name of storage server
	 * @param storageIpAddr
	 *            the storage server ip address
	 * @return true for success, false for fail
	 */
	public boolean deleteStorage(String groupName, String storageIpAddr)
			throws IOException {
		return this.deleteStorage(ClientGlobal.g_tracker_group, groupName,
				storageIpAddr);
	}

	/**
	 * delete a storage server from the FastDFS cluster
	 * 
	 * @param trackerGroup
	 *            the tracker server group
	 * @param groupName
	 *            the group name of storage server
	 * @param storageIpAddr
	 *            the storage server ip address
	 * @return true for success, false for fail
	 */
	public boolean deleteStorage(TrackerGroup trackerGroup, String groupName,
			String storageIpAddr) throws IOException {
		int serverIndex;
		int notFoundCount;
		TrackerServer trackerServer;

		notFoundCount = 0;
		for (serverIndex = 0; serverIndex < trackerGroup.tracker_servers.length; serverIndex++) {
			try {
				trackerServer = trackerGroup.getConnection(serverIndex);
			} catch (IOException ex) {
				ex.printStackTrace(System.err);
				this.errno = ProtoCommon.ECONNREFUSED;
				return false;
			}

			try {
				StructStorageStat[] storageStats = listStorages(trackerServer,
						groupName, storageIpAddr);
				if (storageStats == null) {
					if (this.errno == ProtoCommon.ERR_NO_ENOENT) {
						notFoundCount++;
					} else {
						return false;
					}
				} else if (storageStats.length == 0) {
					notFoundCount++;
				} else if (storageStats[0].getStatus() == ProtoCommon.FDFS_STORAGE_STATUS_ONLINE
						|| storageStats[0].getStatus() == ProtoCommon.FDFS_STORAGE_STATUS_ACTIVE) {
					this.errno = ProtoCommon.ERR_NO_EBUSY;
					return false;
				}
			} finally {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}
		}

		if (notFoundCount == trackerGroup.tracker_servers.length) {
			this.errno = ProtoCommon.ERR_NO_ENOENT;
			return false;
		}

		notFoundCount = 0;
		for (serverIndex = 0; serverIndex < trackerGroup.tracker_servers.length; serverIndex++) {
			try {
				trackerServer = trackerGroup.getConnection(serverIndex);
			} catch (IOException ex) {
				System.err.println("connect to server "
						+ trackerGroup.tracker_servers[serverIndex]
								.getAddress().getHostAddress() + ":"
						+ trackerGroup.tracker_servers[serverIndex].getPort()
						+ " fail");
				ex.printStackTrace(System.err);
				this.errno = ProtoCommon.ECONNREFUSED;
				return false;
			}

			try {
				if (!this
						.deleteStorage(trackerServer, groupName, storageIpAddr)) {
					if (this.errno != 0) {
						if (this.errno == ProtoCommon.ERR_NO_ENOENT) {
							notFoundCount++;
						} else if (this.errno != ProtoCommon.ERR_NO_EALREADY) {
							return false;
						}
					}
				}
			} finally {
				try {
					trackerServer.close();
				} catch (IOException ex1) {
					ex1.printStackTrace();
				}
			}
		}

		if (notFoundCount == trackerGroup.tracker_servers.length) {
			this.errno = ProtoCommon.ERR_NO_ENOENT;
			return false;
		}

		if (this.errno == ProtoCommon.ERR_NO_ENOENT) {
			this.errno = 0;
		}

		return this.errno == 0;
	}
}
